<template>

  <div class="cam-box">

    <div v-show="!videoLock" >
      <video
        id="video"
        ref="video"
        :width="width"
        :height="height"
        playsinline="true"
        autoplay="true" />

      <canvas
        id="canvas"
        ref="canvas"
        :width="width"
        :height="height" />
    </div>

    <div v-show="videoLock" >
      <img :src="captureImage" >
    </div>

    <div v-if="paddingTop >0" :style="`padding-top:${paddingTop}px`">

      <el-select v-model="deviceId" size="mini" style="width: 100%;">
        <el-option
          v-for="item in cameras"
          :key="item.deviceId"
          :value="item.deviceId"
          :label="item.label"/>
      </el-select>

      <div v-if="tracking && showTrackTips && trackingMsg" class="tips">
        {{ trackingMsg }}
      </div>

    </div>

  </div>

</template>

<script>
// 引入tracking
import 'tracking/build/tracking-min.js'
import 'tracking/build/data/face-min.js'

export default {
  name: 'TrackingCam',
  props: {
    width: {
      type: [Number, String],
      default: '100%'
    },
    height: {
      type: [Number, String],
      default: 'auto'
    },
    captureFormat: {
      type: String,
      default: 'image/jpeg'
    },
    // 是否开启面部追踪
    tracking: {
      type: Boolean,
      default: false
    },
    // 是否在追踪到人脸后截图
    trackCapture: {
      type: Boolean,
      default: true
    },
    // 是否展示追踪提示
    showTrackTips: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      source: null,
      canvas: null,
      context: null,
      camerasListEmitted: false,
      cameras: [],
      // 摄像头ID
      deviceId: null,
      videoLock: false,
      // 内容偏移
      paddingTop: 0,
      // 人脸识别相关
      tracker: null,
      trackingMsg: '',
      captureLock: false,
      captureImage: null,
      // 警告定时器
      faceTimer: null,
      loading: false
    }
  },

  // 切换摄像头
  watch: {
    deviceId: function(val) {
      if (val) {
        this.changeCamera(val)
      }
    }
  },
  mounted() {
    this.setupMedia()
  },
  beforeDestroy() {
    this.stop()

    // 停止面部追踪
    if (this.tracker) {
      this.tracker.removeListener('track', this.handleTracked)
      // eslint-disable-next-line no-undef
      tracking.track(this.$refs.video, this.tracker, { camera: false })
      this.tracker = null
    }
  },
  methods: {

    // 获取媒体设备
    legacyGetUserMediaSupport() {
      return constraints => {
        const getUserMedia =
          navigator.getUserMedia ||
          navigator.webkitGetUserMedia ||
          navigator.mozGetUserMedia ||
          navigator.msGetUserMedia ||
          navigator.oGetUserMedia
        if (!getUserMedia) {
          return Promise.reject(
            new Error('您的浏览器不支持媒体访问，请升级浏览器！')
          )
        }
        // 旧版浏览器
        return new Promise(function(resolve, reject) {
          getUserMedia.call(navigator, constraints, resolve, reject)
        })
      }
    },

    // 初始化媒体设备
    setupMedia() {
      if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {}
      }
      if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = this.legacyGetUserMediaSupport()
      }

      // 测试并获得授权
      this.testMediaAccess()
    },

    // 读取可用的媒体设备
    loadCameras() {
      navigator.mediaDevices
        .enumerateDevices()
        .then(deviceInfos => {
          for (let i = 0; i !== deviceInfos.length; ++i) {
            const deviceInfo = deviceInfos[i]
            if (deviceInfo.kind === 'videoinput') {
              this.cameras.push(deviceInfo)
            }
          }
        })
        .then(() => {
          if (!this.camerasListEmitted) {
            if (this.cameras.length > 0) {
              this.deviceId = this.cameras[0].deviceId
            }
            this.$emit('cameras', this.cameras)
            this.camerasListEmitted = true
          }
        })
        .catch(error => this.$emit('notsupported', error))
    },

    // 更换不同的摄像头, 比如前置摄像头、后置摄像头等
    changeCamera(deviceId) {
      this.stop()
      this.$emit('camera-change', deviceId)
      this.loadCamera(deviceId)
    },

    // 加载视频流
    loadSrcStream(stream) {
      if ('srcObject' in this.$refs.video) {
        this.$refs.video.srcObject = stream
      } else {
        this.source = window.HTMLMediaElement.srcObject(stream)
      }

      // 开始播放视频
      this.$refs.video.onloadedmetadata = () => {
        this.$emit('video-live', stream)

        // 视频下方内容位置
        this.paddingTop = document.querySelector('#video').clientHeight + 5

        // 进行面部追踪
        if (this.tracking && !this.tracker) {
          this.startTracking()
        }
      }
      this.$emit('started', stream)
    },

    // 停止流读取
    stopStreamedVideo(videoElem) {
      const stream = videoElem.srcObject
      const tracks = stream.getTracks()
      tracks.forEach(track => {
        track.stop()
        this.$emit('stopped', stream)
        this.$refs.video.srcObject = null
        this.source = null
      })
    },
    // 停止播放
    stop() {
      if (this.$refs.video !== null && this.$refs.video.srcObject) {
        this.stopStreamedVideo(this.$refs.video)
      }
    },
    // 开始播放
    start() {
      if (this.deviceId) {
        this.loadCamera(this.deviceId)
      }
    },
    // 暂停播放
    pause() {
      if (this.$refs.video !== null && this.$refs.video.srcObject) {
        this.$refs.video.pause()
      }
    },
    // 继续视频
    resume() {
      if (this.$refs.video !== null && this.$refs.video.srcObject) {
        this.$refs.video.play()
      }
    },
    /**
     * test access
     */
    testMediaAccess() {
      const constraints = { video: true }
      if (this.resolution) {
        constraints.video = {}
        constraints.video.height = this.resolution.height
        constraints.video.width = this.resolution.width
      }
      navigator.mediaDevices
        .getUserMedia(constraints)
        .then(stream => {
          const tracks = stream.getTracks()
          tracks.forEach(track => {
            track.stop()
          })
          this.loadCameras()
        })
        .catch(error => this.$emit('error', error))
    },

    // 指定媒体设备进行
    loadCamera(device) {
      // 清除识别框
      if (this.context && this.canvas) {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)
      }

      const constraints = { video: { deviceId: { exact: device }, facingMode: 'user' }}
      if (this.resolution) {
        constraints.video.height = this.resolution.height
        constraints.video.width = this.resolution.width
      }
      navigator.mediaDevices
        .getUserMedia(constraints)
        .then(stream => this.loadSrcStream(stream))
        .catch(error => this.$emit('error', error))
    },

    // 静默截屏，截屏时候无感知
    capture() {
      const video = this.$refs.video
      // 直接截图
      const canvas = document.createElement('canvas')
      canvas.height = video.videoHeight
      canvas.width = video.videoWidth
      const ctx = canvas.getContext('2d')
      ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
      const dataImage = canvas.toDataURL(this.captureFormat)
      // 转换成base64去除头
      const base64 = dataImage.replace(/^data:image\/\w+;base64,/, '')
      this.$emit('capture', base64)
      return base64
    },

    // 开始面部追踪
    startTracking() {
      // 初始化画布用于截图和人脸跟踪
      this.canvas = document.getElementById('canvas')
      this.canvas.width = document.querySelector('#video').clientWidth
      this.canvas.height = document.querySelector('#video').clientHeight
      this.context = this.canvas.getContext('2d')

      // eslint-disable-next-line no-undef
      this.tracker = new tracking.ObjectTracker('face')
      this.tracker.setInitialScale(2)
      this.tracker.setStepSize(1)
      this.tracker.setEdgesDensity(0.1)

      // eslint-disable-next-line no-undef
      tracking.track(this.$refs.video, this.tracker, { camera: true })
      this.tracker.on('track', this.handleTracked)
    },

    // 进行人脸识别
    handleTracked(event) {
      // 显示截图
      if (this.videoLock) {
        return
      }

      // 清除识别框
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height)

      // 没有人脸
      if (event.data.length === 0) {
        this.trackingMsg = '未识别到人脸，请将人脸置于摄像头中央！'
        // 启动人脸警告
        this.showFaceOut()
        return
      }

      // 清空人脸警告
      this.clearFaceOut()
      this.trackingMsg = '很好，请保持这个姿势！'

      // 延时截屏
      if (!this.captureLock && this.trackCapture) {
        this.captureLock = true
        // 停留片刻截屏
        setTimeout(this.handleCapture, 2000)
      }

      event.data.forEach(this.plot)
    },

    // 人脸识别警告
    showFaceOut() {
      if (this.faceTimer) {
        return
      }

      this.faceTimer = window.setInterval(() => {
        this.$emit('face-out')
      }, 10000)
    },

    // 清空人脸检测
    clearFaceOut() {
      if (this.faceTimer) {
        window.clearInterval(this.faceTimer)
        this.faceTimer = null
      }
    },

    // 画人脸识别框
    plot(rect) {
      this.context.strokeStyle = '#FF7026'
      this.context.lineWidth = 2
      this.context.font = '12px Helvetica'
      this.context.strokeRect(rect.x, rect.y, rect.width, rect.height)
      this.context.fillStyle = '#111'
    },

    // 人脸截图
    handleCapture() {
      // 按画布大小进行截图覆盖
      this.context.drawImage(this.$refs.video, 0, 0, this.canvas.width, this.canvas.height)
      const image = this.canvas.toDataURL(this.captureFormat)

      // 显示截图，隐藏视频
      this.videoLock = true

      this.trackingMsg = '正在比对，请稍候..'
      this.captureImage = image

      // 回调人脸截图
      const base64 = image.replace(/^data:image\/\w+;base64,/, '')
      this.$emit('tracked', base64)
    },

    // 重新追踪人脸并截图
    reTrack() {
      this.videoLock = false
      this.captureLock = false
    }
  }
}
</script>

<style scoped>

.cam-box{
  position: relative;
  width: 100%;
  height: auto;
  margin-bottom: 10px;
}

video, canvas, img {
  position: absolute;
  left: 0px;
  top: 0px;
}

.tips{
  margin-top: 10px;
  font-size: 12px;
  font-weight: 700;
}
</style>
